package LWJGL_testing;

import org.lwjgl.opencl.Util;
import org.lwjgl.opencl.CLMem;
import org.lwjgl.opencl.CLCommandQueue;
import org.lwjgl.BufferUtils;
import org.lwjgl.PointerBuffer;
import org.lwjgl.opencl.CLProgram;
import org.lwjgl.opencl.CLKernel;

import java.nio.FloatBuffer;
import java.util.List;

import org.lwjgl.opencl.CL;
import org.lwjgl.opencl.CLContext;
import org.lwjgl.opencl.CLDevice;
import org.lwjgl.opencl.CLPlatform;

import static org.lwjgl.opencl.CL10.*;

public class Main {
	// The OpenCL kernel
	static final String source =
		"kernel void sum(global const float *a, global const float *b, global float *answer) { "
		+ "  unsigned int xid = get_global_id(0); "
		+ "  if (a[xid] == b[0]) {"
		+ "    answer[0] = xid;" 
		+ "  }"
		+ "}";

	// Data buffers to store the input and result data in
	static int len = 16777217;
	static int searchFor = 1;
	static final FloatBuffer a = toFloatBuffer(ascArray(len));
	static final FloatBuffer b = toFloatBuffer(new float[]{searchFor});
	static final FloatBuffer answer = BufferUtils.createFloatBuffer(a.capacity());

	public static void main(String[] args) throws Exception {
		// Initialize OpenCL and create a context and command queue
		CL.create();
		// 0 : Intel(R) OpenCL, 1 : NVIDIA CUDA
		CLPlatform platform = CLPlatform.getPlatforms().get(0);
		System.out.println(platform.getInfoString(CL_PLATFORM_NAME));
		List<CLDevice> devices = platform.getDevices(CL_DEVICE_TYPE_GPU);
		CLContext context = CLContext.create(platform, devices, null, null, null);
		CLCommandQueue queue = clCreateCommandQueue(context, devices.get(0), CL_QUEUE_PROFILING_ENABLE, null);

		// Allocate memory for our two input buffers and our result buffer
		CLMem aMem = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, a, null);
		clEnqueueWriteBuffer(queue, aMem, 1, 0, a, null, null);
		CLMem bMem = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, b, null);
		clEnqueueWriteBuffer(queue, bMem, 1, 0, b, null, null);
		CLMem answerMem = clCreateBuffer(context, CL_MEM_WRITE_ONLY | CL_MEM_COPY_HOST_PTR, answer, null);
		clFinish(queue);

		// Create our program and kernel
		CLProgram program = clCreateProgramWithSource(context, source, null);
		Util.checkCLError(clBuildProgram(program, devices.get(0), "", null));
		// sum has to match a kernel method name in the OpenCL source
		CLKernel kernel = clCreateKernel(program, "sum", null);

		// Execution our kernel
		PointerBuffer kernel1DGlobalWorkSize = BufferUtils.createPointerBuffer(1);
		kernel1DGlobalWorkSize.put(0, a.capacity());
		kernel.setArg(0, aMem);
		kernel.setArg(1, bMem);
		kernel.setArg(2, answerMem);
		
		long start = System.nanoTime();
		clEnqueueNDRangeKernel(queue, kernel, 1, null, kernel1DGlobalWorkSize, null, null, null);
		
		// Read the results memory back into our result buffer
		clEnqueueReadBuffer(queue, answerMem, 1, 0, answer, null, null);
		clFinish(queue);
		long end = System.nanoTime();
		
		// Print the result memory
		print(a);
		System.out.print("\nfind ");
		print(b);
		System.out.println("\nresult");
		float result = answer.get(0);
		System.out.println("a["+result+"] = "+a.get((int) result));
		System.out.println("\nTook: " + (end-start) + " nanos");

		// Clean up OpenCL resources
		clReleaseKernel(kernel);
		clReleaseProgram(program);
		clReleaseMemObject(aMem);
		clReleaseMemObject(bMem);
		clReleaseMemObject(answerMem);
		clReleaseCommandQueue(queue);
		clReleaseContext(context);
		CL.destroy();
		
		float[] aa = ascArray(16777217);
		long start2 = System.nanoTime();
		int index = findIndexOf(568709, aa);
		long end2 = System.nanoTime();
		System.out.println("\n\na["+index+"] = "+aa[index]);
		System.out.println("\nTook: " + (end2-start2) + " nanos");
	}

	public static int findIndexOf(float val, float[] arr){
		int l = 0;
		int r = arr.length-1;
		int mid = r/2;
		if (val == arr[mid]) return mid;
		if (val > arr[mid]){
			return findHelper(val, arr, mid, r);
		}
		return findHelper(val, arr, l, mid);
	}
	
	public static int findHelper(float val, float[] arr, int l, int r){
		if (l+1==r && arr[l] != val) return -1;
		int mid = (l+r)/2;
		if (val == arr[mid]) return mid;
		if (val > arr[mid]){
			return findHelper(val, arr, mid, r);
		}
		return findHelper(val, arr, l, mid);
	}
	

	/** Utility method to convert float array to float buffer
	 * @param floats - the float array to convert
	 * @return a float buffer containing the input float array
	 */
	static FloatBuffer toFloatBuffer(float[] floats) {
		FloatBuffer buf = BufferUtils.createFloatBuffer(floats.length).put(floats);
		buf.rewind();
		return buf;
	}


	/** Utility method to print a float buffer
	 * @param buffer - the float buffer to print to System.out
	 */
	static void print(FloatBuffer buffer) {
		int i;
		for (i = 0; i < buffer.capacity() && i < 10; i++) {
			System.out.print(buffer.get(i)+" ");
		}
		if (i < buffer.capacity()){
			System.out.print("... "+buffer.get(buffer.capacity()-1));
		}
		System.out.println("");
	}
	
	public static float[] ascArray(int length){
		float[] result = new float[length];
		for (float i = 0; i < length; i++){
			result[(int) i] = i+0;
		}
		return result;
	}
	
	public static float[] descArray(int length){
		float[] result = new float[length];
		for (float i = 0; i < length; i++){
			result[(int) i] = length - i - 1.0f;
		}
		return result;
	}

}